#!/usr/bin/env python
"""Add a Boot Information Structure (BIS) to hexfiles.

usage: anubis.py  [infile] [outfile] [configfile] [opts].

examples: 

   anubis.py  input.hex  output.hex  address=0x800001AC
       Write BIS of to output.hex . The BIS_ADDRESS will
       be written to address 0x800001AC.

   anubis.py  input.hex segments=[0x08000000,0x08004000,0x08008000] 
       Write BIS of to out.hex (defaultname). The BIS_ADDRESS will   
       be written relative to the start address.
       The hexfile will be extendet until a segment address.

   anubis.py  input.hex +read_bis
       Print information about BIS of infile.
"""
__author__      = "Michael J. Hohmann"
__copyright__   = "Copyright 2017, Linde Material Handling GmbH"
__version__       = "0.0.3"
__email__       = "michael.hohmann@lind-mh.de"
__status__      = "Prototype"
__date__        = "2017-12-05"

import os
import sys
import intelhex
import time
import crcstm32
import argopt
import behavior
import swid
import util
import xutil

    

def read_bis( ihex, address ):
    rv={}
    rv['bis_version']   = xutil.get_u32(ihex,address )
    address+=4
    rv['crc_start']     = xutil.get_u32(ihex,address )
    address+=4
    rv['crc_len']       = xutil.get_u32(ihex,address )
    address+=4
    rv['crc_val']       = xutil.get_u32(ihex,address )
    address+=4
    rv['build_id']      = xutil.get_u32(ihex,address )
    address+=4
    rv['diag_vers']     = xutil.get_u32(ihex,address )
    address+=4
    rv['reserve']       = xutil.get_u32(ihex,address )
    address+=4
    rv['checksum']      = xutil.get_u32(ihex,address )   
    return rv
       
def print_bis( bis_version, crc_start, crc_len, crc_val, build_id, diag_vers, reserve, checksum, prefix=''):
    print( prefix+"bis_version= "+hex(bis_version))
    print( prefix+"crc_start  = "+hex(crc_start  ))
    print( prefix+"crc_len    = "+hex(crc_len    ))
    print( prefix+"crc_val    = "+hex(crc_val    ))
    print( prefix+"build_id   = "+hex(build_id   ))
    print( prefix+"diag_vers  = "+hex(diag_vers  ))
    print( prefix+"reserve    = "+hex(reserve    ))
    print( prefix+"checksum   = "+hex(checksum   )   )

def write_bis_header(ihex, address, bis_version, crc_start, crc_len, crc_val, build_id, diag_vers, reserve , crc_func):
    first_address= address
    xutil.put_u32(ihex,address, bis_version )
    address+=4
    xutil.put_u32(ihex,address, crc_start   )
    address+=4
    xutil.put_u32(ihex,address, crc_len   )
    address+=4
    xutil.put_u32(ihex,address, crc_val   )
    address+=4
    xutil.put_u32(ihex,address, build_id   )
    address+=4
    xutil.put_u32(ihex,address, diag_vers   )
    address+=4
    xutil.put_u32(ihex,address, reserve   )
    address+=4
    checksum=crc_func( ihex.tobinarray(first_address , first_address+28-1))
    xutil.put_u32(ihex,address, checksum  ) 
    return checksum

def get_segment_end(segment_list, address ):
    for segment in segment_list:
        if segment > address:
            return segment
    return address
        
        
def blow_up( ihex, end_address, pattern=None ):
    if pattern == None:
        pattern = behavior.pattern

    segments=ihex.segments()
    last_address=segments[-1][1]
    cursor_ihex = last_address
    cursor_pattern=0
    patternlen=len(pattern)
    
    while cursor_ihex < end_address:
        ihex[cursor_ihex]=pattern[cursor_pattern]
        cursor_ihex+=1
        cursor_pattern+=1
        cursor_pattern%=patternlen
                       
def print_help(optnames):
    print( str(__doc__))
    print( "The following options are available:")
    for item in optnames:
        print( '  - '+item['name']+'\n         '+item['comment'])
   
def print_opts(opts):
    print( "Working with the following options:" )
    for item in opts.keys():
        if type(opts[item])==int:
            value = hex(opts[item])
        else:
            value = opts[item]
        
        print( '    '+item+' = '+str(value))
    print("")
    
    
optnames=[
        { 'name':'outfile_name'         ,'type':str,  'default':'out.hex','comment':'Name of the output file.' },
        { 'name':'infile_name'          ,'type':str,  'default':'in.hex', 'comment':'Name of the input file.'},
        { 'name':'address'              ,'type':int,  'default':None,     'comment':'Address of BIS-Address.'},
        { 'name':'offset'               ,'type':int,  'default':0x188,    'comment':'Offset BIS-Address.'},
        { 'name':'segments'             ,'type':list, 'default':None,     'comment':'List of segement addresses.'},
        { 'name':'alignment'           ,'type':int,   'default':None,     'comment':'Align size of hex file to a multiply of this value.'},
        { 'name':'bis_version'          ,'type':int,  'default':0x1,      'comment':'Version number of the BIS.'},
        { 'name':'build_time'           ,'type':int,  'default':int(time.time()), 'comment':'Build time stamp.'},
        { 'name':'diagnostic_version'   ,'type':int,  'default':0x1,      'comment':'Diagnostic version number.'},
        { 'name':'reserved'             ,'type':int,  'default':0x0,      'comment':'Value of the reserved field. '},
        { 'name':'load_config'          ,'type':bool, 'default':None,     'comment':'Load default config file. '},
        { 'name':'config_file'          ,'type':str,  'default':None,     'comment':'Load config file.'},
        { 'name':'fill_pattern'         ,'type':list, 'default':None,     'comment':'Use value to fill unused sections.'},
        { 'name':'read_bis'             ,'type':bool, 'default':False,    'comment':'Read BIS from infile.'},
        { 'name':'signature'            ,'type':list, 'default':None,     'comment':'add signature.'},
    ]
    
    
def parse_cmdline( argv ):
    p=argopt.argopt(optnames)
    unknown,args,opts=p.parameter( sys.argv )

    if len(unknown) > 0:
        print( "Unknown arguments detected.")
        print("")
        print( unknown)
        print_help(optnames)
        exit(0)
    
    if len(sys.argv)==1:
        print_help(optnames)
        exit(0)
    
    if len(args)>=1: # if one argument interpret it as filename
        opts['infile_name']=args[0]
    if len(args)>=2: # if one argument interpret it as filename            
        opts['outfile_name']=args[1]
    if len(args)>=3: # if one argument interpret it as filename            
        opts['config_file']=args[2]
    if len(args)>=4: # if one argument interpret it as filename                  
        print( "Warning - Only 3 arguments are processed.")
        print( "    Ingnoring arguments:"+str(args[3:]))
        print("")
    
    if opts['load_config']==True:
        opts['config_file']=util.change_file_ending(opts['infile_name'],'py')
        print( 'Option [load_config] is set' )
        print( '   Set option [config_file] to '+opts['config_file'])
        print("")
        
    if opts['config_file']!=None:
        print( 'Option [config_file] is set')
        print( "   Load config file "+opts['config_file'])
        print("")
        p.merge_cfg_file( opts['config_file'] )

    print_opts(opts)
    return unknown,args,opts
  
def read_and_check_bis( opts,ihex ):
        bis_address= xutil.get_u32(ihex,opts['address'])     
        bis_info = read_bis(ihex,bis_address) 

        print( "Read BIS from hexfile address "+hex(bis_address))
        print_bis( prefix='    ',**bis_info )
        print("")
        
        print( "Build ID")
        print( "    build_id   ="+hex(bis_info['build_id']))
        print( "    swid       ="+'"'+swid.from_seconds(bis_info['build_id'])+'"')
        print( "    gmtime     ="+time.asctime(time.gmtime(bis_info['build_id']))  )
        print("")
        #check checksum
        bis_chks =crcstm32.crcstm32( ihex.tobinarray(bis_address , bis_address-1+28))
        if bis_info['checksum']==bis_chks:
            print( "Checksum of BIS is correct.")
        else:
            print( "=========================================================")
            print( "ERROR checksum of BIS is wrong.")
            print( "=========================================================")
        print( "    exptected  ="+hex(bis_info['checksum']))
        print( "    calculated ="+hex(bis_chks))
        print("")

        application_crc=crcstm32.crcstm32( ihex.tobinarray(bis_info['crc_start'] ,bis_info['crc_start']+bis_info['crc_len']*4-1))
        if bis_info['crc_val']==application_crc:
            print( "Checksum of application is correct.")
        else:
            print( "=========================================================")
            print( " ERROR checksum of application is wrong.")
            print( "=========================================================")
        print( "    exptected crc  ="+hex(bis_info['crc_val']))
        print( "    calculated crc ="+hex(application_crc))
        print("")

        
def write_bis_address( dest_address, value ):
   
    current_value= xutil.get_u32( ihex, dest_address )
    if current_value != 0 and current_value!=0xFFFFFFFF:
        print( "=========================================================")
        print( "   Warning - Value of address "+hex(opts['address']))
        print( "   is not empty. Current value is "+hex(current_value))
        print( "   The values 0x00000000 or 0xFFFFFFFF are expected.")
        print( "   It's likely that the output file is corrupted !")
        print( "=========================================================")
        print("")
    print( "Write BIS_ADDRESS:")
    print( "    address =",hex(dest_address))
    print( "    value   =",hex(value))
    print("")
    xutil.put_u32( ihex, dest_address,value )

def alignment_size_of_hexfile( inthex, alignment, pattern  ):  
    if alignment != None: 
        last_address=xutil.last_address(ihex)
        sizeof_bis=32
        if ( (last_address+sizeof_bis) % alignment) !=0 : # if hexfile is not aligned
            new_size = ( ( int((last_address + sizeof_bis) / alignment ) + 1) * alignment ) - sizeof_bis
            print( "Align size of hexfile to "+ hex(new_size) )
            print("")
            blow_up( inthex, new_size , pattern )

def segments_blow_up( inthex, segments , pattern ):
    if segments != None:
        sizeof_bis=32
        new_size = get_segment_end( segments, xutil.last_address(inthex)+sizeof_bis ) - sizeof_bis
        print( "Blow up hexfile to next segment")
        print( '    size = '+hex(new_size))
        print("")
        blow_up(inthex, new_size , pattern )

def signature_add( inthex, signature ):
    if signature !=None:
        print( "Add signature",str(signature))
        print("")      
        cursor_ihex = xutil.last_address(inthex)
        for element in signature:
            inthex[cursor_ihex]=element
            cursor_ihex+=1
  
  
if __name__=="__main__":
    print( "================================[ ANUBIS V."+__version__+"]===============================")

    # process parameter
    unknown,args,opts=parse_cmdline( sys.argv )
    
    # load hexfile
    filename=opts['infile_name']
    print( "Read Hex-File"+filename)
    ihex=intelhex.IntelHex( filename )
    
    # see how big the hex file is
    data_lenght = int((xutil.last_address(ihex)-xutil.first_address(ihex))/4)
    print( "    start  ="+ hex(xutil.first_address(ihex)))
    print( "    ends   ="+ hex(xutil.last_address(ihex)-1))
    print( "    length ="+ hex(data_lenght*4)+" = "+hex(data_lenght)+" 32Bit-Words")
    print("")
     
    #write last address of hex file to bis_address
    if opts['address']==None:
        opts['address']=xutil.first_address(ihex)+opts['offset']
        print( "No address specified." )
        print( "    address   = begin of hexfile + offset")
        print("")

    # Read BIS
    if opts['read_bis'] == True:
        read_and_check_bis(opts,ihex)
        exit(0)        
    
    # blow up hexfile until next segment border
    segments_blow_up( ihex,opts['segments'],opts['fill_pattern'] )
    
    # Align size of hexfile
    alignment_size_of_hexfile( ihex, opts['alignment'], opts['fill_pattern'] )

    # write bis address
    write_bis_address( opts['address'], xutil.last_address(ihex) )
  
    #write BIS  
    crc_val     = crcstm32.crcstm32( ihex.tobinarray( xutil.first_address(ihex)  , xutil.last_address(ihex)-1))

    print( "Add BIS to address "+hex(xutil.last_address(ihex)))
    print( "    bis_version  = "+hex(opts['bis_version'] ))
    print( "    crc_start    = "+hex(xutil.first_address(ihex)))
    print( "    crc_len      = "+hex(int((xutil.last_address(ihex)-xutil.first_address(ihex))/4)))
    print( "    crc_val      = "+hex(crc_val))
    print( "    build_id     = "+hex(opts['build_time']))
    print( "    diag_vers    = "+hex(opts['diagnostic_version']))
    print( "    reserve      = "+hex(opts['reserved']))

    
    bis_checksum=write_bis_header(ihex, 
        address     = xutil.last_address(ihex),
        bis_version = opts['bis_version'] ,
        crc_start   = xutil.first_address(ihex) ,
        crc_len     = int((xutil.last_address(ihex)-xutil.first_address(ihex))/4),
        crc_val     = crc_val,
        build_id    = opts['build_time'],
        diag_vers   = opts['diagnostic_version'],
        reserve     = opts['reserved'],
        crc_func    = crcstm32.crcstm32 )
    
    print( "    bis_checksum = "+hex(bis_checksum))
    print("")

    
    # Add additional signature at the end      
    signature_add( ihex, opts['signature'] )       

    print( "Hexfile ends now at "+hex(xutil.last_address(ihex)-1))
    print("")

    outfile=opts['outfile_name']
    print( 'ANUBIS writes mummified hex-file to "'+outfile+'"')
    ihex.write_hex_file(outfile)
        
        
   